package stone926.mods.more_enchantments.mixins;

import com.mojang.datafixers.util.Either;
import net.minecraft.block.BlockState;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.attribute.EntityAttribute;
import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.entity.player.ItemCooldownManager;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.tag.FluidTags;
import net.minecraft.util.Unit;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import stone926.mods.more_enchantments.MoreEnchantmentsMod;
import stone926.mods.more_enchantments.damage.StoneEntityDamageSource;
import stone926.mods.more_enchantments.enchantments.*;
import stone926.mods.more_enchantments.enchantments.legendary.FateEnchantment;
import stone926.mods.more_enchantments.interfaces.IFateCooldown;
import stone926.mods.more_enchantments.util.BlockUtil;
import stone926.mods.more_enchantments.util.DamageUtil;
import stone926.mods.more_enchantments.util.EnchantmentUtil;
import stone926.mods.more_enchantments.util.RandomUtil;

import static stone926.mods.more_enchantments.enchantments.CooldownShorterEnchantment.getShorterCooldown;

@Mixin(PlayerEntity.class)
public abstract class PlayerEntityMixin extends LivingEntity {

  private float i = -1;

  protected PlayerEntityMixin(EntityType<? extends LivingEntity> entityType, World world) {
    super(entityType, world);
  }

  @Shadow
  protected abstract void damageArmor(DamageSource source, float amount);

  @Shadow
  public abstract Iterable<ItemStack> getArmorItems();

  @Shadow
  public abstract float getAttackCooldownProgress(float baseTime);

  @Shadow
  public abstract Either<PlayerEntity.SleepFailureReason, Unit> trySleep(BlockPos pos);

  @Inject(method = "takeShieldHit", at = @At("HEAD"))
  protected void takeShieldHit(LivingEntity attacker, CallbackInfo ci) {
    ItemStack activeItem = getActiveItem();
    int bounceLvl = EnchantmentHelper.getLevel(MoreEnchantmentsMod.BOUNCE, activeItem);
    int counterattackLvl = EnchantmentHelper.getLevel(MoreEnchantmentsMod.COUNTERATTACK, activeItem);
    if (bounceLvl > 0) {
      attacker.takeKnockback(BounceEnchantment.getKnockbackStrength(bounceLvl), this.getX() - attacker.getX(), this.getZ() - attacker
        .getZ());
      attacker.setVelocity(attacker.getVelocity().add(0, BounceEnchantment.getYVelocity(bounceLvl), 0));
    }
    if (counterattackLvl > 0) {
      attacker.damage(StoneEntityDamageSource.counterattack((PlayerEntity) (Object) this), CounterattackEnchantment.getDamage(bounceLvl));
    }
  }

  @Redirect(method = "disableShield", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/ItemCooldownManager;set(Lnet/minecraft/item/Item;I)V"))
  private void disableShieldHit(ItemCooldownManager manager, Item item, int duration) {
    int shorterLvl = EnchantmentHelper.getLevel(MoreEnchantmentsMod.COOLDOWN_SHORTER, getActiveItem());
    manager.set(item, (int) Math.round(duration * getShorterCooldown(shorterLvl)));
  }

  @ModifyVariable(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;getAttackCooldownProgress(F)F"), ordinal = 1)
  private float attack(float h, Entity target) {
    return h + DamageUtil.getExtraDamageAmount(this, target, h);
  }

  @ModifyVariable(method = "getBlockBreakingSpeed", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/entity/player/PlayerInventory;getBlockBreakingSpeed(Lnet/minecraft/block/BlockState;)F"), ordinal = 0)
  private float getBlockBreakingSpeed(float f) {
    int lvl = EnchantmentHelper.getEquipmentLevel(MoreEnchantmentsMod.AQUA_REJECTION, this);
    if (lvl > 0 && this.isSubmergedIn(FluidTags.WATER)) return f / (lvl + 0.5F);
    else return f;
  }

  @Redirect(method = "getAttackCooldownProgressPerTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;getAttributeValue(Lnet/minecraft/entity/attribute/EntityAttribute;)D"))
  public double redirectGetAttributeValue(PlayerEntity playerEntity, EntityAttribute attribute) {
    int onslaughtLvl = EnchantmentHelper.getEquipmentLevel(MoreEnchantmentsMod.ONSLAUGHT, this);
    StatusEffectInstance effect = getStatusEffect(StatusEffects.MINING_FATIGUE);
    return this.getAttributeValue(EntityAttributes.GENERIC_ATTACK_SPEED) + OnslaughtEnchantment.getAttackSpeedAddition(onslaughtLvl, effect == null ? 0 : effect.getAmplifier());
  }

  @Inject(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;resetLastAttackedTicks()V"))
  private void attack(Entity target, CallbackInfo ci) {
    i = getAttackCooldownProgress(0.5F);
  }

  @Redirect(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;damage(Lnet/minecraft/entity/damage/DamageSource;F)Z"))
  private boolean attackLivingEntity(Entity target, DamageSource source, float amount) {
    boolean damaged = target.damage(source, amount);
    if (damaged && i > 0.9F) {
      int decapitationLvl = EnchantmentHelper.getEquipmentLevel(MoreEnchantmentsMod.DECAPITATION, this);
      int vampireLvl = EnchantmentHelper.getEquipmentLevel(MoreEnchantmentsMod.VAMPIRE, this);
      RandomUtil.runIfPossible(DecapitationEnchantment.getDecapitationProbability(decapitationLvl, target), random, () -> DecapitationEnchantment.decapitate(this, target, true));
      RandomUtil.runIfPossible(VampireEnchantment.getHealProbability(vampireLvl), random, () -> VampireEnchantment.heal(this, target, vampireLvl));
    }
    return damaged;
  }

  @Redirect(method = "applyDamage", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;setHealth(F)V"))
  private void fate(PlayerEntity victim, float health, DamageSource source, float amount) {
    int fateCooldown = ((IFateCooldown) this).getFateCooldown();
    if (fateCooldown == 0 && health <= 0 && !DamageUtil.isDecapitation(source) && !source.isOutOfWorld()) {
      int i = EnchantmentHelper.getEquipmentLevel(MoreEnchantmentsMod.FATE, victim);
      if (i > 0) {
        ((IFateCooldown) this).setFateCooldown(FateEnchantment.getCooldownTicks(EnchantmentUtil.getTotalEquipmentLvl(MoreEnchantmentsMod.FATE, victim)));
        if (world instanceof ServerWorld server) {
          server.spawnParticles(
            ParticleTypes.TOTEM_OF_UNDYING,
            victim.getX(), victim.getY() + victim.getEyeHeight(victim.getPose()), victim.getZ(),
            100, 0.3, 0.3, 0.3, 0.4
          );
        }
        victim.addStatusEffect(new StatusEffectInstance(StatusEffects.RESISTANCE, RandomUtil.nextInt(60, 120, random), 6));
        victim.addStatusEffect(new StatusEffectInstance(StatusEffects.STRENGTH, RandomUtil.nextInt(50, 100, random), RandomUtil
          .nextInt(1, 3, random)));
        victim.addStatusEffect(new StatusEffectInstance(StatusEffects.INVISIBILITY, RandomUtil.nextInt(200, 250, random), 1));
        return;
      }
    }
    victim.setHealth(health);
  }

  @Inject(method = "canHarvest", at = @At("HEAD"), cancellable = true)
  private void canHarvest(BlockState state, CallbackInfoReturnable<Boolean> cir) {
    if (BlockUtil.canUseCompressedStonePickaxe((PlayerEntity) (Object) this, state)) {
      cir.setReturnValue(true);
    }
  }

}